#include "Solver.h"


CGcontext context;

void cgErrorCallback()
{
	CGerror lastError = cgGetError();
	if(lastError) {
		const char *listing = cgGetLastListing(context);
		printf("%s\n", cgGetErrorString(lastError));
		printf("%s\n", listing);
		exit(-1);
	}
}

Solver::Solver(void)
{
}

Solver::~Solver(void)
{
}

void Solver::SolverInit( int width, int height, GLenum interFormat, GLenum format, GLuint type, GLenum target/*=GL_TEXTURE_RECTANGLE_ARB*/ )
{
	width+=2;
	height+=2;

	framebuffer.width=width;
	framebuffer.height=height;
	framebuffer.interFormat=interFormat;
	framebuffer.format=format;
	framebuffer.type=type;
	framebuffer.target=target;

	renderTarget.FBOInit(framebuffer);

	density=new VideoBuffer();
	velocity=new VideoBuffer();
	source=new VideoBuffer();

	density->BufferInit(framebuffer);
	velocity->BufferInit(framebuffer);
	source->BufferInit(framebuffer);

	float *density_cpu=(float*)malloc(width*height*3*sizeof(float));
	float *velocity_cpu=(float*)malloc(width*height*3*sizeof(float));
	float *source_cpu=(float*)malloc(width*height*3*sizeof(float));

	//memset(density_cpu,1,width*height*3*sizeof(float));
	//memset(pre_density_cpu,0,width*height*3*sizeof(float));
	//memset(velocity_cpu,0,width*height*3*sizeof(float));
	//memset(pre_velocity_cpu,0,width*height*3*sizeof(float));


	for (int i=0;i<width;i++)
	{
		for (int j=0;j<height;j++)
		{
			velocity_cpu[(j*width+i)*3]=velocity_cpu[(j*width+i)*3+1]=velocity_cpu[(j*width+i)*3+2]=0;
			density_cpu[(j*width+i)*3]=density_cpu[(j*width+i)*3+1]=density_cpu[(j*width+i)*3+2]=0;
			source_cpu[(j*width+i)*3]=source_cpu[(j*width+i)*3+1]=source_cpu[(j*width+i)*3+2]=0;
		}
	}

	for (int i=29;i<30;i++)
	{
		for (int j=29;j<30;j++)
		{
			source_cpu[(j*width+i)*3]=source_cpu[(j*width+i)*3+1]=source_cpu[(j*width+i)*3+2]=100;
		}
	}


	density->AllocateMemory(density_cpu);
	density->AllocateCacheMemory();
	velocity->AllocateMemory(velocity_cpu);
	velocity->AllocateCacheMemory();
	source->AllocateMemory(source_cpu);

	applyForce.Load("AP.cg",CG_GL_FRAGMENT);
	velAdvect.Load("VA.cg",CG_GL_FRAGMENT);
	addSource.Load("AS.cg",CG_GL_FRAGMENT);
	denAdvect.Load("DA.cg",CG_GL_FRAGMENT);
	denToCPU.Load("CPU.cg",CG_GL_FRAGMENT);
	velProject.Load("VP.cg",CG_GL_FRAGMENT);
	//vortConfine.Load("VC.cg",CG_GL_FRAGMENT);
	//velUpdate.Load("VU.cg",CG_GL_FRAGMENT);

	addSource.AddParameter("source",CG_GL_TEXTURE_RECT,&(source->bufferID));
	addSource.AddParameter("density",CG_GL_TEXTURE_RECT,&(density->bufferID));
	addSource.AddParameter("width",CG_GL_VECTOR_1D,&(framebuffer.width));
	addSource.AddParameter("height",CG_GL_VECTOR_1D,&(framebuffer.height));
	addSource.AddParameter("dt",CG_GL_VECTOR_1D,&dt);

	applyForce.AddParameter("density",CG_GL_TEXTURE_RECT,&(density->bufferID));
	applyForce.AddParameter("velocity",CG_GL_TEXTURE_RECT,&(velocity->bufferID));
	applyForce.AddParameter("tamb",CG_GL_VECTOR_1D,&buoy_tamb);
	applyForce.AddParameter("a",CG_GL_VECTOR_1D,&buoy_a);
	applyForce.AddParameter("b",CG_GL_VECTOR_1D,&buoy_b);
	applyForce.AddParameter("width",CG_GL_VECTOR_1D,&(framebuffer.width));
	applyForce.AddParameter("height",CG_GL_VECTOR_1D,&(framebuffer.height));
	applyForce.AddParameter("dt",CG_GL_VECTOR_1D,&dt);

	//velUpdate.AddParameter("pre_velocity",CG_GL_TEXTURE_RECT,&(pre_velocity->bufferID));

	velAdvect.AddParameter("velocity",CG_GL_TEXTURE_RECT,&(velocity->bufferID));
	velAdvect.AddParameter("width",CG_GL_VECTOR_1D,&(framebuffer.width));
	velAdvect.AddParameter("height",CG_GL_VECTOR_1D,&(framebuffer.height));
	velAdvect.AddParameter("dt",CG_GL_VECTOR_1D,&(dt));

	denAdvect.AddParameter("velocity",CG_GL_TEXTURE_RECT,&(velocity->bufferID));
	denAdvect.AddParameter("density",CG_GL_TEXTURE_RECT,&(density->bufferID));
	denAdvect.AddParameter("width",CG_GL_VECTOR_1D,&(framebuffer.width));
	denAdvect.AddParameter("height",CG_GL_VECTOR_1D,&(framebuffer.height));
	denAdvect.AddParameter("dt",CG_GL_VECTOR_1D,&dt);

	denToCPU.AddParameter("density",CG_GL_TEXTURE_RECT,&(density->bufferID));

	//vortConfine.AddParameter("pre_velocity",CG_GL_TEXTURE_RECT,&(pre_velocity->bufferID));
}

void Solver::SolverUpdate()
{
	dt=0.2;
	ApplyBuoyancy();
	velocity->SwapMemory();
	ApplyVelAdvection();
	density->SwapMemory();
	AddSource();
	density->SwapMemory();
	ApplyDenAdvection();
	density->SwapMemory();
	GetDensity();
}

void Solver::SolverProcessing()
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, framebuffer.width, 0.0, framebuffer.height);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glViewport(0, 0, framebuffer.width, framebuffer.height);

	glPolygonMode(GL_FRONT,GL_FILL);

	glBegin(GL_QUADS);
	glTexCoord2f(0.0, 0.0); 
	glVertex2f(0.0, 0.0);
	glTexCoord2f(framebuffer.width, 0.0); 
	glVertex2f(framebuffer.width, 0.0);
	glTexCoord2f(framebuffer.width, framebuffer.height); 
	glVertex2f(framebuffer.width, framebuffer.height);
	glTexCoord2f(0.0, framebuffer.height); 
	glVertex2f(0.0, framebuffer.height);
	glEnd();
}

void Solver::ApplyBuoyancy()
{
	buoy_tamb=0;
	buoy_a=0.000625f;
	buoy_b=0.025f;
	dt=0.2;


	for (int i=1;i<=framebuffer.width-2;i++)
	{
		for (int j=1;j<=framebuffer.height-2;j++)
		{
			buoy_tamb+=density->buffer[3*((j*framebuffer.width)+i)];
		}
	}

	buoy_tamb/=((framebuffer.width-2)*(framebuffer.height-2));

	renderTarget.FBOActivate(velocity->cacheID);

	applyForce.ShaderBind();

	SolverProcessing();

	applyForce.ShaderRelease();

	renderTarget.FBODeactivate();
}

void Solver::ApplyVelAdvection()
{
	renderTarget.FBOActivate(velocity->cacheID);

	velAdvect.ShaderBind();

	SolverProcessing();

	velAdvect.ShaderRelease();

	renderTarget.FBODeactivate();
}

void Solver::AddSource()
{
	renderTarget.FBOActivate(density->cacheID);

	addSource.ShaderBind();

	SolverProcessing();

	addSource.ShaderRelease();

	renderTarget.FBODeactivate();
}

void Solver::ApplyDenAdvection()
{
	renderTarget.FBOActivate(density->cacheID);

	denAdvect.ShaderBind();

	SolverProcessing();

	denAdvect.ShaderRelease();

	renderTarget.FBODeactivate();
}

void Solver::GetDensity()
{
	renderTarget.FBOActivate(density->cacheID);

	denToCPU.ShaderBind();

	SolverProcessing();

	denToCPU.ShaderRelease();

	glReadPixels(0, 0, framebuffer.width, framebuffer.height,GL_RGB,GL_FLOAT,density->buffer);

	renderTarget.FBODeactivate();
}

void VideoBuffer::BufferInit( GPUFBO &framebuffer )
{
	this->framebuffer=framebuffer;	
}

void VideoBuffer::AllocateMemory(float *data)
{
	buffer=data;

	glGenTextures(1,&bufferID);
	glBindTexture(framebuffer.target,bufferID);
	glTexParameteri(framebuffer.target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(framebuffer.target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(framebuffer.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(framebuffer.target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexImage2D(framebuffer.target, 0, framebuffer.interFormat, framebuffer.width, framebuffer.height, 0, framebuffer.format, framebuffer.type, data);

}

void VideoBuffer::AllocateCacheMemory()
{
	glGenTextures(1,&cacheID);
	glBindTexture(framebuffer.target,cacheID);
	glTexParameteri(framebuffer.target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(framebuffer.target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(framebuffer.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(framebuffer.target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexImage2D(framebuffer.target, 0, framebuffer.interFormat, framebuffer.width, framebuffer.height, 0, framebuffer.format, framebuffer.type, NULL);

}

void VideoBuffer::SwapMemory()
{
	GLuint temp=bufferID;
	bufferID=cacheID;
	cacheID=temp;
}

ShaderObject::ShaderObject()
{
	shaderProgram = NULL;
	shaderContext = NULL;
	shaderParameters.clear();
}

bool ShaderObject::Load( const char *filename, CGGLenum type )
{
	shaderParameters.clear();

	cgSetErrorCallback(cgErrorCallback);

	context=shaderContext=cgCreateContext();

	shaderName.clear();
	shaderName.append(filename);
	shaderType = type;
	shaderProfile = cgGLGetLatestProfile(type);

	if(shaderType == CG_GL_VERTEX){
		shaderProgram = cgCreateProgramFromFile(shaderContext, CG_SOURCE, shaderName.c_str(), shaderProfile, NULL, NULL);
	}else if(shaderType == CG_GL_FRAGMENT){
		shaderProgram = cgCreateProgramFromFile(shaderContext, CG_SOURCE, shaderName.c_str(), shaderProfile, NULL, NULL);
	}
	cgGetProgramString(shaderProgram, CG_COMPILED_PROGRAM);

	if(shaderProgram != NULL)
	{
		cgGLLoadProgram(shaderProgram);
		if(shaderProgram == NULL)
			return false;
		return true;
	}
	else
		return false;
}

void ShaderObject::Unload()
{
	cgDestroyProgram(shaderProgram);
}

void ShaderObject::AddParameter( char *name, CGGL_PARAMETER_TYPE type, void *ptr )
{
	if(shaderProgram == NULL) return;
	GPUParam newParam;

	if(ptr != NULL)
		newParam.ptrs = ptr;

	newParam.type = type;
	newParam.name.append(name);
	newParam.param = cgGetNamedParameter(shaderProgram, newParam.name.c_str());

	shaderParameters.push_back(newParam);
}

void ShaderObject::RefreshParameter()
{
	int	tex_id = 1;
	int* id;
	float* value;
	for(int i=0; i<shaderParameters.size(); i++)
	{
		switch(shaderParameters[i].type)
		{
		case CG_GL_VECTOR_1D:
			cgGLSetParameter1fv(shaderParameters[i].param, (float *)shaderParameters[i].ptrs);
			value = (float*)shaderParameters[i].ptrs;
			break;
		case CG_GL_VECTOR_2D:
			cgGLSetParameter2fv(shaderParameters[i].param, (float *)shaderParameters[i].ptrs);
			break;
		case CG_GL_VECTOR_3D:
			cgGLSetParameter3fv(shaderParameters[i].param, (float *)shaderParameters[i].ptrs);
			break;
		case CG_GL_VECTOR_4D:
			cgGLSetParameter4fv(shaderParameters[i].param, (float *)shaderParameters[i].ptrs);
			value = (float*)shaderParameters[i].ptrs;
			break;
		case CG_GL_MODELVIEW_MTX:
			cgGLSetStateMatrixParameter(shaderParameters[i].param, CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_IDENTITY);
			break;
		case CG_GL_MODELVIEW_PROJECTION_MTX:
			cgGLSetStateMatrixParameter(shaderParameters[i].param, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
			break;
		case CG_GL_MODELVIEW_IT_MTX:
			cgGLSetStateMatrixParameter(shaderParameters[i].param, CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_INVERSE_TRANSPOSE);
			break;
		case CG_GL_ARBITRARY_MTX:
			cgGLSetMatrixParameterfc(shaderParameters[i].param, (float *)shaderParameters[i].ptrs);
			value = (float *)shaderParameters[i].ptrs;
			break;
		case CG_GL_TEXTURE_2D:
		case CG_GL_TEXTURE_CUBE_MAP:
		case CG_GL_TEXTURE_RECT:
			id = (int*)shaderParameters[i].ptrs; 
			cgGLSetTextureParameter(shaderParameters[i].param, *id);
			cgGLEnableTextureParameter(shaderParameters[i].param);
			break;
		}
	}
}

void ShaderObject::ShaderBind()
{
	if(this->shaderProgram)
	{
		cgGLBindProgram(this->shaderProgram);
		cgGLEnableProfile(this->shaderProfile);
		this->RefreshParameter();
	}
}

void ShaderObject::ShaderRelease()
{
	if(this->shaderProgram)
		cgGLDisableProfile(this->shaderProfile);
}

void FrameBufferObject::FBOInit( GPUFBO &framebuffer )
{
	this->framebuffer=framebuffer;

	glewInit();

	glGenFramebuffersEXT(1,&framebufferID);
}

void FrameBufferObject::FBOActivate( GLuint &texID )
{
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,framebufferID);

	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT0_EXT,framebuffer.target,texID,NULL);	

}

void FrameBufferObject::FBODeactivate()
{
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}
